package eu.webhib.frmwrk.service.impl;

import eu.webhib.frmwrk.dao.intf.BaseDao;
import eu.webhib.frmwrk.dao.DaoFactory;
import eu.webhib.frmwrk.domain.intf.BaseEntity;
import eu.webhib.frmwrk.persistence.PersistenceMngr;
import eu.webhib.frmwrk.service.intf.BaseService;
import eu.webhib.frmwrk.service.utils.RangeData;
import eu.webhib.frmwrk.utils.BeanUtils;
import java.util.List;
import java.lang.reflect.ParameterizedType;
import org.apache.log4j.Logger;

/**
 * Basic service implementation for managing the persistence state of entities
 * @param <T> type of entity managed by this service
 */
public class BaseServiceImpl<T extends BaseEntity> implements BaseService<T> {

   private static final Logger log = Logger.getLogger(BaseServiceImpl.class);
   /* the data access obejct */
   protected BaseDao<T> dao;

   /* only used by a derived class */
   @SuppressWarnings("unchecked")
   public BaseServiceImpl() {
      Class<T> persistentClass = (Class<T>) ((ParameterizedType) getClass().
              getGenericSuperclass()).getActualTypeArguments()[0];
      this.dao = DaoFactory.instance().<T>createDao(persistentClass);
   }

   public BaseServiceImpl(BaseDao<T> dao) {
      this.dao = dao;
   }

   @Override
   public T createNew() {
      T entity = dao.createNew();
      log.debug("createNew: " + entity);
      return entity;
   }

   @Override
   public void save(T entity) {
      log.debug("save: " + entity.getClass().getSimpleName());

      PersistenceMngr.beginTransaction();
      // business logic
      dao.persist(entity);
      log.debug("save: saved " + entity);
   }

   @Override
   public T update(T entity) {
      log.debug("update: " + entity);

      PersistenceMngr.beginTransaction();
      // business logic
      return dao.update(entity);
   }

   @Override
   public void updateFromCopy(T entityToChange, T changedEntity) {
      log.debug("updateFromCopy: " + changedEntity);

      assert entityToChange != null;
      assert changedEntity != null;
      assert entityToChange.getId().equals(changedEntity.getId());

      BeanUtils.copyEntity(entityToChange, changedEntity);
      update(entityToChange);
   }

   @Override
   public void delete(T entity) {
      log.debug("delete: " + entity);

      PersistenceMngr.beginTransaction();
      // business logic
      dao.delete(entity);
   }

   // caller has to begin and commit the transaction
   @Override
   public void deleteAll() {
      log.debug("deleteAll");

      PersistenceMngr.beginTransaction();
      // business logic
      dao.deleteAll();
   }

   @Override
   public T findId(Long id) {
      log.debug("findId: " + id);
      T entity;

      // business logic
      entity = dao.findById(id);
      return entity;
   }

   @Override
   public List<T> findByNamedQuerySuffix(String queryNameSuffix, Object... paramValue) {
      log.debug("findByNamedQuerySuffix");
      List<T> entities;

      // business logic
      entities = dao.findByNamedQuerySuffix(queryNameSuffix, paramValue);
      return entities;
   }

   @Override
   public List<T> findByAttribute(String attrName, Object attrValue) {
      log.debug("findByAttribute");
      List<T> entities;

      // business logic
      entities = dao.findByAttribute(attrName, attrValue);
      return entities;
   }

   @Override
   public T findByAttributeSingle(String attrName, Object attrValue) {
      log.debug("findByAttributeSingle");

      List<T> entities = findByAttribute(attrName, attrValue);
      if (entities.isEmpty()) {
         return null;
      }
      assert (entities.size() == 1);
      return entities.get(0);
   }

   @Override
   public List<T> findAll() {
      log.debug("findAll");
      List<T> entities;

      // business logic
      entities = dao.findAll();
      return entities;
   }

   @Override
   public RangeData<T> findAllRange(int startIndex, int size) {
      log.debug("findAllRange: " + startIndex + ":" + size);
      RangeData<T> range;

      // business logic
      range = findRangeInternal(startIndex, size);
      return range;
   }

   private RangeData<T> findRangeInternal(int startIndex, int size) {
      int totalCount = dao.count();
      // if requested range is not available, recalculate start index in order to fetch last page
      if (totalCount <= startIndex) {
         startIndex = (totalCount - 1) / size * size;
      }
      log.debug("findRangeService: " + startIndex + " totalCount: " + totalCount);
      return new RangeData<T>(
              dao.findAllRange(startIndex, size),
              startIndex,
              totalCount);
   }

   @Override
   public int count() {
      return dao.count();
   }
}
